/**
 * Copyright (c) 2006-2012 Las Venturas Playground, LVP Mineground
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package mineground.core;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * This class provides the implementation of the message loop used by Las Venturas Mineground. Most
 * publically exposed methods are defined in the MessageLoop interface, which mainly exists to
 * easen use for developers not too familiar with the actual workings of the loop.
 * 
 * The Message Loop posts tasks to other threads, each of which will be defined as a private inner
 * class. Tasks store basic information depending on their kind, and allow status checking and re-
 * insertion by the executor may that be necessary. Messages are more flexible in their kind but
 * do require reflection to be invoked, and may be invoked multiple times as well.
 */
public class MessageLoopImpl implements MessageLoop {
    /**
     * What is the maximum number of tasks that may be executed per invocation of the run() method?
     * This limit exists to make sure that we can pause this frequently enough to not hog the
     * server's CPU too much, even when the queue is fairly big. If CPU usage of the main process
     * is low while results of commands (i.e. messages) are severaly delayed, increase this number.
     */
    private final static int MaximumTasksPerInvocation = 50;
    
    /**
     * Even when a synchronous task for some reason locks up, we don't want the entire gamemode to
     * shut down. Impose a maximum wait time on these calls after which NULL will be returned.
     */
    private final static int SynchronousTaskTimeout = 1000;

    /**
     * In order to make sure that we don't overflow buffers with a ton of messages destinated for a
     * thread that does not exist (yet), maintain a registry of the threads which are available and
     * are polling the Message Loop.
     */
    private HashMap<MessageLoopThread, Boolean> mThreadRegistry;

    /**
     * As execution time within the Message Loop is critical, prioritize the listing of pending
     * tasks within the queue based on their estimated next execution time. The PriorityQueue will
     * do this automatically using the following comparator. By utilizing a sorted queue for this
     * purpose, we can implement delayed and repetitive tasks with almost no overhead.
     */
    private class TaskExecutionScheduleComperator implements Comparator<Task> {
        /**
         * Compare two MessageLoopTasks based on their next execution time.
         */
        @Override
        public int compare(Task left, Task right) {
            return left.nextExecutionTime() < right.nextExecutionTime() ? -1 : 1;
        }
    }
    
    /**
     * The Task class is our internal representation of a stored Task, containing information on
     * whether it should be executed, is repetitive or delayed. This class may be subclassed to
     * more specialized behavior depending on the type of task.
     */
    private class Task {
        /**
         * The Runnable which is owned by this Task, and thus should be executed on a thread.
         */
        private Runnable mRunnable;
        
        /**
         * What is the next scheduled execution time for this task? A value of zero means that it
         * should be executed as soon as possible, other values are assumed to be timestamps.
         */
        protected long mNextExecutionTime;
        
        /**
         * Initializing a new basic Task will only require us to know about the runnable right now,
         * as they'll be executed immediately after the thread polls.
         * 
         * @param runnable The Runnable instance which should be executed.
         */
        public Task(Runnable runnable) {
            mRunnable = runnable;
            mNextExecutionTime = 0;
        }
        
        /**
         * Provide access to the runnable object contained by this task. No processing is being
         * done in this method, execution and potential return values have to be controlled by
         * the executor.
         */
        public Runnable runnable() {
            return mRunnable;
        }
        
        /**
         * When should the task be executed, in milliseconds? For basic tasks, this will always
         * be zero as we want to execute them as soon as possible. Delayed tasks will have their
         * execution time calculated in the constructor, as will repetitive tasks.
         */
        public long nextExecutionTime() {
            return mNextExecutionTime;
        }
        
        /**
         * Is this task a repeating task? Repeating tasks have to be re-added after their execution
         * by the thread itself. Basic and delayed tasks are not subject to this.
         */
        public boolean isRepeatingTask() {
            return false;
        }
    }
    
    /**
     * Delayed tasks are a way of sending a message to another thread which is to be executed
     * after a predefined amount of time. They will be disposed of after executing.
     */
    private class DelayedTask extends Task {
        /**
         * Initialize a delayed task which is to be executed after a certain amount of milliseconds,
         * which will be taken care of by the chosen thread.
         * 
         * @param runnable The Runnable instance which should be executed.
         */
        public DelayedTask(Runnable runnable, long delayInMilliseconds) {
            super(runnable);
            
            // Set the next execution time based on the current system time.
            mNextExecutionTime = System.currentTimeMillis() + delayInMilliseconds;
        }
    }
    
    /**
     * The MessageLoopRepeatingTask is used to store repeating tasks in the thread-specific queues,
     * and contains logic telling the executor to reschedule the task after executing it.
     */
    private class RepeatingTask extends DelayedTask {
        /**
         * What is the interval at which this task will be executing? This will be used to increment
         * the time in the scheduleNextExecutionAndReturnSelf method.
         */
        private long mExecutionInterval;
        
        /**
         * Since this task can be cancelled from any given thread, maintain the cancellation state
         * and take this into account just before executing this task.
         */
        private boolean mHasBeenCancelled;
        
        /**
         * Initialize a delayed task which is to be executed after a certain amount of milliseconds,
         * which will be taken care of by the chosen thread.
         * 
         * @param runnable The Runnable instance which should be executed.
         */
        public RepeatingTask(Runnable runnable, long intervalInMilliseconds) {
            super(runnable, intervalInMilliseconds);
            mExecutionInterval = intervalInMilliseconds;
            mHasBeenCancelled = false;
        }
        
        /**
         * Schedule the next invocation of this task based on the given interval and return ourself
         * to the callee, allowing us to conveniently be re-queued for the thread.
         */
        public RepeatingTask scheduleNextExecutionAndReturnSelf() {
            mNextExecutionTime = System.currentTimeMillis() + mExecutionInterval;
            return this;
        }
        
        /**
         * As the task poller will automatically remove tasks from the queue to avoid blocking up
         * other threads, repeating tasks will be re-queued after their invocation.
         */
        @Override
        public boolean isRepeatingTask() {
            return true;
        }
        
        /**
         * Has this task been cancelled? We'll try to prevent executing this task again if it has,
         * and it won't be re-scheduled afterwards anymore.
         */
        public boolean hasBeenCancelled() {
            return mHasBeenCancelled;
        }
        
        /**
         * Cancel future executions of this task, only to be done through cancelRepeatingTask().
         */
        public void cancel() {
            mHasBeenCancelled = true;
        }
    }
    
    /**
     * The pending tasks for the message loop are being stored as a priority queue per thread in the
     * following map. There are two ways of accessing this information: writing will be done by the
     * postMessage() methods, while reading is exclusive to retrievePendingTasksForThread().
     */
    private HashMap<MessageLoopThread, PriorityQueue<Task> > mPerThreadTaskQueue = new
        HashMap<MessageLoopThread, PriorityQueue<Task> >();
    
    /**
     * Maintain a HashMap of all registered repeating tasks, allowing us to cancel them at any
     * given point. Canceling a task involves setting the setting on the task itself.
     */
    private HashMap<Integer, RepeatingTask> mRepeatingTaskRegistry = new
        HashMap<Integer, RepeatingTask>();
    
    /**
     * Since we don't want to store references to all repeating tasks, preventing the memory from
     * being freed, we'll just keep a counter to keep track of the increments.
     */
    private int mLastRepeatingTaskId = 0;
    
    /**
     * Messages will be routed to their module instances on the right threads by the Message Router,
     * the active instance of which will be stored in this property (but is not owned by us).
     */
    private MessageRouter mMessageRouter;
    
    /**
     * Initialize the Message Loop by making sure that all variables are ready for usage. We also
     * reset the states of all known threads to unavailable, blocking messages until we're ready,
     * and make sure that we'll be using the right message router.
     * 
     * @param messageRouter The Message Router provided by the Module Registry.
     */
    public MessageLoopImpl(MessageRouter messageRouter) {
        mThreadRegistry = new HashMap<MessageLoopThread, Boolean>();
        for (MessageLoopThread threadId : MessageLoopThread.values()) {
            mThreadRegistry.put(threadId, false);
        }
        
        // Manually mark the Server Thread as available, considering that's what we are being
        // initialized on right now. Without that thread we'd be a head-less gamemode.
        mThreadRegistry.put(MessageLoopThread.ServerThread, true);
        
        mMessageRouter = messageRouter;
    }
    
    /**
     * Actually run queued items for the current thread in the Message Loop. This method can be
     * called from any thread at any time, and can even run multiple multiple times at the same
     * time. A maximum number of tasks will be executed per invocation of this method.
     * 
     * Keep the amount of executed code in this method to an absolute minimum, as it will be
     * called approximately 60 times per second per thread, depending on the granularity and
     * resolution of the Worker Threads. Early returns here are a good thing.
     * 
     * @param thread The thread for which pending tasks should be executed.
     */
    public void run(MessageLoopThread thread) {
        List<Task> pendingTasks = retrievePendingTasksForThread(thread);
        if (pendingTasks.size() == 0)
            return;
        
        for (int index = 0, length = pendingTasks.size(); index < length; ++index) {
            Task currentTask = pendingTasks.get(index);
            if (currentTask.isRepeatingTask()) {
                RepeatingTask repeatingTask = (RepeatingTask) currentTask;
                if (repeatingTask.hasBeenCancelled())
                    continue;
                
                internalPostTask(thread, repeatingTask.scheduleNextExecutionAndReturnSelf());
            }
            
            currentTask.runnable().run();
        }
    }
    
    /**
     * Remove one or more pending tasks from the task queue and return them as a list, allowing
     * the thread they're intended for to execute them in order. A predefined maximum number
     * of tasks will be returned, as defined in the static int earlier in this class.
     * 
     * @param thread The thread for which tasks should be returned.
     * @return A list of MessageLoopTasks which should be executed on this thread.
     */
    private List<Task> retrievePendingTasksForThread(MessageLoopThread thread) {
        List<Task> taskList = new ArrayList<Task>();
        long currentTime = System.currentTimeMillis();
        
        synchronized (mPerThreadTaskQueue) {
            if (mPerThreadTaskQueue.containsKey(thread) == false)
                return taskList;
            
            PriorityQueue<Task> threadTaskQueue = mPerThreadTaskQueue.get(thread);
            if (threadTaskQueue.size() == 0)
                return taskList;
            
            for (Iterator<Task> iter = threadTaskQueue.iterator(); iter.hasNext(); ) {
                Task currentTask = iter.next();
                if (currentTask.nextExecutionTime() > currentTime || 
                        taskList.size() > MaximumTasksPerInvocation)
                    return taskList;
                
                taskList.add(currentTask);
                iter.remove();
            }
        }
        
        return taskList;
    }
    
    /**
     * Schedule a task on a certain thread which is to be executed as soon as possible.
     * 
     * @param targetThread Thread on which the given runnable should be run.
     * @param runnable The runnable which should be executed on that thread.
     */
    public void postTask(MessageLoopThread targetThread, Runnable runnable) {
        internalPostTask(targetThread, new Task(runnable));
    }
    
    /**
     * Schedule a delayed task which should be executed after a certain number of milliseconds,
     * based on the current system time.
     * 
     * @param targetThread Thread on which the given runnable should be run.
     * @param runnable The runnable which should be executed on that thread.
     * @param delayInMilliseconds After how much milliseconds should this code be executed?
     */
    public void postDelayedTask(MessageLoopThread targetThread, Runnable runnable,
                                int delayInMilliseconds) {
        internalPostTask(targetThread, new DelayedTask(runnable, delayInMilliseconds));
    }
    
    /**
     * Schedule a task which is to be executed at a specific interval, first invocation of which
     * will occur after the actual interval time has been exceeded.
     * 
     * @param targetThread The thread on which the repetitive task should be executed.
     * @param runnable The runnable which should be executed following this interval.
     * @param intervalInMilliseconds The number of milliseconds between each invocation.
     * @return integer An incrementing Id for this task, allowing it to be cancelled.
     */
    public int postRepeatingTask(MessageLoopThread targetThread, Runnable runnable,
                                 int intervalInMilliseconds) {
        RepeatingTask task = new RepeatingTask(runnable, intervalInMilliseconds);
        int taskId = -1;
        
        synchronized (mRepeatingTaskRegistry) {
            taskId = mLastRepeatingTaskId++;
            mRepeatingTaskRegistry.put(taskId, task);
        }
        
        internalPostTask(targetThread, task);
        return taskId;
    }
    
    /**
     * Cancel a repeating task indicated by its Id. Given the semantics of running multiple threads
     * and us not guarding against it, it is possible for the task to execute once more after
     * invoking this method, but in general that should be extremely rare.
     * 
     * @param repeatingTaskId Id of the task which should be cancelled.
     */
    public void cancelRepeatingTask(int repeatingTaskId) {
        synchronized (mRepeatingTaskRegistry) {
            if (mRepeatingTaskRegistry.containsKey(repeatingTaskId) == false)
                return;
            
            mRepeatingTaskRegistry.get(repeatingTaskId).cancel();
        }
    }
    
    /**
     * Schedule a task to be ran on the indicated thread and wait until it has been executed, after
     * which the return value will be returned to the caller. Usage of this function is discouraged.
     * 
     * @param targetThread Thread on which the task should be executed.
     * @param task The task (as a Callable) which should be executed on that thread.
     * @return The return value from the Callable.
     */
    public <Type> Type postSynchronousTask(MessageLoopThread targetThread, Callable<Type> task) {
        final FutureTask<Type> futureTask = new FutureTask<Type>(task);
        internalPostTask(targetThread, new Task(new Runnable() {
            public void run() {
                futureTask.run();
            }
         }));
        
        try {
            return futureTask.get(SynchronousTaskTimeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            return null;
        }
    }
    
    /**
     * Actually posting a task to the thread's queue has been generalized in the following method,
     * to which each call to the post*Task methods will end up.
     * 
     * @param targetThread Thread on which the given runnable should be run.
     * @param task The task which has to be queued for this thread.
     */
    private void internalPostTask(MessageLoopThread targetThread, Task task) {
        synchronized (mPerThreadTaskQueue) {
            if (mPerThreadTaskQueue.containsKey(targetThread) == false) {
                mPerThreadTaskQueue.put(targetThread,
                    new PriorityQueue<Task>(25, new TaskExecutionScheduleComperator()));
            }
            
            mPerThreadTaskQueue.get(targetThread).add(task);
        }
    }
    
    /**
     * 
     * 
     */
    public void postMessage(MessageLoopThread targetThread, final Message message,
                            final Object... params) {
        // @TODO Implement this method properly.
        postTask(targetThread, new Runnable() {
            public void run() {
                mMessageRouter.invokeHandlers(message, params);
            }
        });
    }
    
    /**
     * Update the availability of a certain thread, identified by it's Id. When a thread is not
     * available, tasks being sent to it will be disregarded instead. The only place where this
     * method should be called is from within a WorkerThread's run() method.
     * 
     * @param thread The thread for which the availability should be updated.
     * @param isAvailable Starting with this invocation, is the thread available for polling?
     */
    public void updateThreadAvailability(MessageLoopThread thread, boolean isAvailable) {
        synchronized (mThreadRegistry) {
            mThreadRegistry.put(thread, isAvailable);
        }
    }
}
